
现在正是 Linux 全面向桌面进军的时候,Linux 从 Unix 系统继承了许多特征,包括强大的字符用%
户接口,对于计算机技术人员,字符界面是强大,高效,灵活的,但是对于普通桌面用户来讲,这无疑%
是一个非常大的挑战。因此,学习 Linux 系统下的 GUI 程序设计对于致力于推广 Linux 的应用开发人员%
来说是非常有必要的。

Linux 下的 GUI 程序开发有许多选择,可以选择许多种程序设计语言,也有许多种界面开发库可以%
选择,其中最出名的,当然是用于 GNOME 开发的 GTK+和开发了 KDE 和 QT。QT 是一个用于 GUI%
程序设计的 C++库,有良好的设计与封装,可以达到艺术级的应用程序界面。GTK+是用 C 语言开发%
的图形界面开发包,最早用于 GIMP 的开发,后来应用到了我们大家熟悉的 GNOME 桌面系统的开发,%
GTK+使用 C 语言,却是使用了面向对象的设计思想,稳定,高效,效果也非常不错,已有大量的应用%
使用 GTK+开发。

\begin{flushleft}
{\large{}\textbf{实现源代码}}
\end{flushleft}

Main.c 源代码如下:
\begin{shaded}
\begin{verbatim}
/*
 *
 * main.c
 *
 *  Created on: 2008-8-12
 *      Author: zhzhxtrrk
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <gtk/gtk.h>
#define MAX_NUMBER 10
#define MAX_TIMES 4

int times = 0; /*尝试次数*/
int number = 0; /*要猜的数*/

/**
 * guess()
 * 猜数字的逻辑部分,代码很简单,不作讲解
 * @param widget 触发事件的控件
 * @param data 用户数据,要求为entry控件指针
 */
void guess(GtkWidget* widget, gpointer data) {
     if (0 == times) {
          time_t t;
          time(&t);
          srand(t);
          number = random() % MAX_NUMBER;
     }

     int guess = atoi(gtk_entry_get_text(GTK_ENTRY(data)));/*得到文本框的值*/
     gtk_entry_set_text(GTK_ENTRY(data), "");/*置空文本框,方便接下来的操作*/

     char message[1024];
     if (guess > number) {
          if (times >= MAX_TIMES - 1) {
              sprintf(message, "哎,没有机会了!\n答案是%d。", number);
              times = 0;
          } else {
              sprintf(message, "大了点!\n再试试。");
              times++;
          }
     } else if (guess < number) {
          if (times >= MAX_TIMES - 1) {
              sprintf(message, "哎,没有机会了!\n答案是%d。", number);
              times = 0;
          } else {
              sprintf(message, "小了点!\n再试试。");
              times++;
          }
     } else if (guess == number) {
          sprintf(message, "恭喜你!\n答对了!");
          times = 0;
     }

     /**
      * 通常这段代码都是这个组合,弹出一个消息框
      * 要注意的是GTK的消息框不会自动关闭,所以最后一句不可少
      */
     GtkWidget* messageDialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
              GTK_MESSAGE_INFO, GTK_BUTTONS_OK, message); /*创建消息框*/
     gtk_window_set_title(GTK_WINDOW(messageDialog), "猜数字!");/*设置消息框标题*/
     gtk_dialog_run(GTK_DIALOG(messageDialog)); /*弹出消息框*/
     gtk_widget_destroy(messageDialog);/*关闭消息框*/
}

/**
 * createWindow()
 * 创建主界面
 * @return 创建完成的窗口
 */
GtkWidget* createWindow() {
     GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);/*创建窗口*/
     gtk_window_set_title(GTK_WINDOW(window), "猜数字");/*设置窗口标题*/
     g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);/*绑定关闭消息*/

     GtkWidget* hbox = gtk_hbox_new(FALSE, 0);/*创建一个横向的布局盒*/
     gtk_container_add(GTK_CONTAINER(window), hbox);/*将它放到窗口上*/

     GtkWidget* entry = NULL;/*文本框*/
     GtkWidget* button = NULL;/*按钮*/

     entry = gtk_entry_new();/*创建文本框*/
     gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);/*放它放到布局盒里*/

     button = gtk_button_new_with_label("猜!");/*创建按钮*/
     g_signal_connect(button, "clicked", G_CALLBACK(guess), entry);/*绑定单击事件*/
     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);/*放入布局盒中*/

     return window;/*返回窗口*/
}

/**
 * main()
 * 主函数,初始化环境,显示主界面,开始GTK的运行
 * @return 程序状态
 */
int main(int argc, char* argv[]) {
    gtk_init(&argc, &argv);/*初始化GTK环境*/
    gtk_widget_show_all(createWindow());/*显示界面*/
    gtk_main();/*开始运行*/

    return 0;
}
\end{verbatim}
\end{shaded}

makefile 源代码:
\begin{shaded}
\begin{verbatim}
all:
    gcc main.c -o guess `pkg-config --cflags --libs gtk+-2.0`
clean:
    rm main
\end{verbatim}
\end{shaded}

\begin{flushleft}
{\large{}\textbf{编译与运行}}
\end{flushleft}

启动终端,进入源代码文件所在文件夹输入
\begin{shaded}
\begin{verbatim}
make
\end{verbatim}
\end{shaded}

编译源程序,得到可执行文件 guess。在终端里输入
\begin{shaded}
\begin{verbatim}
./guess
\end{verbatim}
\end{shaded}

启动程序。

\begin{flushleft}
{\large{}\textbf{源代码分析}}
\end{flushleft}

如果你有一定的 C 语言基础,根据源代码里的注释,相信你应该能对程序的大致流程有一个比较清%
晰的认识:-)

程序从 \texttt{main()}函数开始,首先初始化 GTK 环境,然后程序调用 \texttt{createWindow()}生成程序的主窗口,%
并将它显示出来,接着进入主循环,主函数的任务到此也就算是结束了,到这里后,程序会停下来,等%
待用户在图形界面上的操作,在图形界面上的操作邮一种叫作回调函数的函数来响应给出相应的结果。%
这和普通的 C 程序设计里有些不同,这也正是图形界面程序的特殊性,因为用户在图形界面上操作有多%
种选择,而且这些选择很多时候没有明显的顺序性,用户的操作往往也有一定的随机性。与其按普通的%
顺序处理的方式来处理这些随机的操作,还不如在操作的时候再来根据操作选择相应的函数来执行,这%
也正是回调函数的精髓所在。所谓回调函数,可以这样理解,用户的操作是一个事件,我们在程序里为%
这个事件注册一个函数,告诉系统在这个事件发生的时候主动的来执行这个函数通知程序这个事件发生%
了,并交由程序作出相应的处理。这个事件发生的时候被调用的函数就叫作回调函数。这种设计在这种%
随机性事件比较普遍的系统中非常常见,而图形界面的程序就是一个典型的例子。

在 \texttt{createWindow()}函数里,调用了 GTK+的一系列函数生成了程序的主界面,并注册了用于处理%
事件的回调函数,程序的注释里说明了具体的做法,更具体的函数用法的说明就请有兴趣的读者自己查%
阅 GTK+的手册了。这段代码用到了布局管理的知识,这在图形界面程序设计里很有价值,相信读者深%
入学习的时候会渐渐发现它的价值,在这里提醒大家学习的时候多注意一下这些布局管理器,灵活运用%
它们,就可以作出非常精美聪明的界面。这里更多的是需要大家去查手册,我需要解释的有两点。
\begin{shaded}
\begin{verbatim}
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);/*绑定关闭消息*/
\end{verbatim}
\end{shaded}

这是第一个需要解释的地方,GTK+的程序在关闭主窗口的时候,虽然主窗口会被释放掉,但是进%
程并不会结束,因此,我们调用这行代码把关闭窗口事件和 GTK+退出主循环的函数 \texttt{gtk\_main\_quit()}%
关联起来,这样,在关闭主窗口的时候,进程会一并结束(严格讲并不是结束进程,还记得我们讲过的%
\texttt{gtk\_main()}吧,从面说到程序执行到 \texttt{gtk\_main()}会停下来,当执行 \texttt{gtk\_main\_quit()}的时候只是从主循%
环函数 \texttt{gtk\_main()}中返回,接着向下执行),多数情况下这是适用的。
\begin{shaded}
\begin{verbatim}
g_signal_connect(button, "clicked", G_CALLBACK(guess), entry);/*绑定单击事件*/
\end{verbatim}
\end{shaded}

这句是这个程序的明星,这行代码把按钮的鼠标点击事件和 \texttt{guess()}函数联系在了一起,当点击按钮%
这个事件发生的时候,回调函数 \texttt{guess()}被执行,处理相应的逻辑。

接下来就是 \texttt{guess()}函数了,这个函数前面提到,会在点击按钮的时候执行,这个函数主要是处理猜%
数字的逻辑,比较简单,也就不多做解释了,配合注释和 GTK+手册,应该不会有多大的问题。不过,%
要提醒的是,对于初学者,要转变下思路,以往通过循环来处理的这种逻辑在用这种随机性事件来触发%
时候在程序代码上的相应调整,转变思路,充分,有效的利用回调函数的特点。

\begin{flushleft}
{\large{}\textbf{总结}}
\end{flushleft}

本文通过一个简单的图形界面猜数字游戏,介绍了一下 GTK+图形界面开发包,并对图形界面程序%
设计给了一点提示,内容不多,仅供入门读者参考,希望能对您有所帮助。

祝大家学习进步,也祝大家能够享受 Linux 带给我们的快乐。

\begin{flushright}
  \hyperref[content]{回到目录}
\end{flushright}